﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace Quaack.Data
{
    public class Profiel
    {

        public int Id
        {
            get { return _id; }
        }

        public string Gebruikersnaam
        {
            get { return _gebruikersnaam; }
        }

        public string Profielschets
        {
            get { return _profielschets; }
        }

        public string Emailadres
        {
            get { return _emailadres; }
        }

        public string Mobiel
        {
            get { return _mobiel; }
        }

        public string AvatarLocatie
        {
            get { return _avatarLocatie; }
            set
            {
                _avatarLocatie = value;

                string query = "UPDATE [profile] SET [avatarlocation] = @avatarLocatie WHERE [profileid] = @id";
                SqlParameter[] parameters = new SqlParameter[] 
                {
                    new SqlParameter("avatarLocatie", _avatarLocatie),
                    new SqlParameter("id", _id),
                };
                Provider.Instance.Execute(query, parameters);
            }
        }

        public bool IsGeverifieerd
        {
            get { return _isGeverifieerd; }
        }


        public string TeVerifierenEmail
        {
            get { return _teVerifierenEmail; }
        }

        public DateTime VerificatieTot
        {
            get { return _verificatieTot; }
        }

        public bool IsGeblokkeerd
        {
            get { return _isGeblokkeerd; }
        }

        public DateTime GeblokkeerdSinds
        {
            get { return _geblokkeerdSinds; }
        }

        public bool IsVerwijderd
        {
            get { return _isVerwijderd; }
        }

        public DateTime VerwijderdSinds
        {
            get { return _verwijderdSinds; }
        }

        private int _id;
        private string _gebruikersnaam;
        private string _profielschets;
        private string _emailadres;
        private string _mobiel;
        private string _avatarLocatie;
        private bool _isGeverifieerd;
        private string _teVerifierenEmail;
        private DateTime _verificatieTot;
        private bool _isGeblokkeerd;
        private DateTime _geblokkeerdSinds;
        private bool _isVerwijderd;
        private DateTime _verwijderdSinds;

        /// <summary>
        /// Te selecteren kolommen uit de database
        /// </summary>
        public static readonly string Select = "[profile].[profileid], [profile].[username], [profile].[profile], [profile].[email], [profile].[avatarlocation], [profile].[mobilenumber], [profile].[verificationpending], [profile].[verificationemail], [profile].[verificationpendingdatetime], [profile].[tempblocked], [profile].[tempblockeddatetime], [profile].[removed], [profile].[removeddatetime]";

        /// <summary>
        /// Leest één rij uit een SqlDataReader en zet deze om in een
        /// Profiel-object
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        public static Profiel FromReader(SqlDataReader reader)
        {
            // Profiel-object aanmaken
            Profiel profiel = new Profiel();

            // Waardes uit de reader halen
            profiel._id = reader.GetInt32(0);
            profiel._gebruikersnaam = reader.GetString(1);
            profiel._profielschets = reader.GetString(2);
            profiel._emailadres = reader.GetString(3);
            profiel._avatarLocatie = reader.GetString(4);
            profiel._mobiel = reader.GetString(5);
            if (reader.GetBoolean(6))
            {
                profiel._isGeverifieerd = false;
                profiel._verificatieTot = reader.GetDateTime(8);
            }
            else if (!reader.IsDBNull(7))
            {
                profiel._isGeverifieerd = true;
                profiel._teVerifierenEmail = reader.GetString(7);
                profiel._verificatieTot = reader.GetDateTime(8);
            }
            else
            {
                profiel._isGeverifieerd = true;
            }
            if (reader.GetBoolean(9))
            {
                profiel._isGeblokkeerd = true;
                profiel._geblokkeerdSinds = reader.GetDateTime(10);
            }
            else
            {
                profiel._isGeblokkeerd = false;
            }
            if (reader.GetBoolean(11))
            {
                profiel._isVerwijderd = true;
                profiel._verwijderdSinds = reader.GetDateTime(12);
            }
            else
            {
                profiel._isVerwijderd = false;
            }

            // Profiel teruggeven
            return profiel;
        }

        public static Profiel FromId(int id)
        {
            // Query opbouwen
            string query = "SELECT " + Select + " FROM [profile] WHERE [profileid] = @id";
            SqlParameter[] parameters = new SqlParameter[] {
                new SqlParameter("id", id)
            };

            // Query uitvoeren en resultaat ophalen
            SqlDataReader reader = Provider.Instance.ExecuteReader(query, parameters);
            Profiel profiel = null;
            if (reader.Read())
            {
                profiel = FromReader(reader);
            }

            // Reader vrijgeven en resultaat (of null) retourneren
            reader.Close();
            return profiel;
        }

        public static Profiel FromGebruikersnaam(string gebruikersnaam)
        {
            // Query opbouwen
            string query = "SELECT " + Select + " FROM [profile] WHERE [username] = @gebruikersnaam";
            SqlParameter[] parameters = new SqlParameter[] {
                new SqlParameter("gebruikersnaam", gebruikersnaam)
            };

            // Query uitvoeren en resultaat ophalen
            SqlDataReader reader = Provider.Instance.ExecuteReader(query, parameters);
            Profiel profiel = null;
            if (reader.Read())
            {
                profiel = FromReader(reader);
            }

            // Reader vrijgeven en resultaat (of null) retourneren
            reader.Close();
            return profiel;
        }


        /// <summary>
        /// Geeft het profiel bij het opgegeven mobiele nummer, of null indien 
        /// deze niet bestaat
        /// </summary>
        /// <param name="mobiel"></param>
        /// <returns></returns>
        public static Profiel FromMobiel(string mobiel)
        {
            // Query opbouwen
            string query = "SELECT " + Select + " FROM [profile] WHERE [mobilenumber] = @mobiel";
            SqlParameter[] parameters = new SqlParameter[] {
                new SqlParameter("mobiel", mobiel)
            };

            // Query uitvoeren en resultaat ophalen
            SqlDataReader reader = Provider.Instance.ExecuteReader(query, parameters);
            Profiel profiel = null;
            if (reader.Read())
            {
                profiel = FromReader(reader);
            }

            // Reader vrijgeven en resultaat (of null) retourneren
            reader.Close();
            return profiel;
        }

        /// <summary>
        /// Zoekt naar een bepaalde gebruiker (in de gebruikersnaam). Geblokkeerde,
        /// verwijderde en niet geverifieerde gebruikers worden niet teruggegeven
        /// </summary>
        /// <param name="zoekterm">De zoekterm waarop gezocht moet worden</param>
        /// <returns>Lijst met profielen</returns>
        public static List<Profiel> Zoeken(string zoekterm)
        {
            // Query opbouwen
            string query = "SELECT "+Select+" FROM [profile] WHERE [username] LIKE @gebruikersnaam AND [verificationpending] = 0 AND [removed] = 0";
            SqlParameter[] parameters = new SqlParameter[] {
                new SqlParameter("gebruikersnaam", "%" + zoekterm + "%")
            };

            // Query uitvoeren en resultaten in een lijst zetten
            SqlDataReader reader = Provider.Instance.ExecuteReader(query, parameters);
            List<Profiel> result = new List<Profiel>();
            while (reader.Read())
            {
                result.Add(FromReader(reader));
            }
            // Reader sluiten, zodat een volgende database-actie kan worden uitgevoerd
            reader.Close();

            // Lijst met profielen teruggeven
            return result;
        }

        /// <summary>
        /// Lege constructor, private omdat deze alleen mag worden aangeroepen
        /// wanneer een profiel uit de database wordt gehaald
        /// </summary>
        private Profiel()
        {
        }

        /// <summary>
        /// Maakt een nieuw profiel aan en slaat deze op in de database
        /// </summary>
        /// <param name="gebruikersnaam"></param>
        /// <param name="profielschets"></param>
        /// <param name="emailadres"></param>
        /// <param name="mobiel"></param>
        /// <param name="avatarLocatie"></param>
        public Profiel(string gebruikersnaam, string profielschets, string emailadres, string mobiel, string avatarLocatie)
        {
            // Waardes opslaan in object
            _gebruikersnaam = gebruikersnaam;
            _profielschets = profielschets;
            _emailadres = emailadres;
            _mobiel = mobiel;
            _avatarLocatie = avatarLocatie;
            _isGeverifieerd = false;
            _verificatieTot = DateTime.Now.AddMinutes(Properties.Settings.Default.VerificationTimeoutMinutes);
            _isGeblokkeerd = false;
            _isVerwijderd = false;
            
            // Query opbouwen
            string query = 
                @"INSERT INTO [profile] ([username], [password], [profile], [email], [avatarlocation], 
                                         [mobilenumber], [verificationpending], [verificationpendingdatetime], 
                                         [tempblocked], [permblocked], [removed]) 
                  VALUES (@gebruikersnaam, '', @profielschets, @email, @avatarLocatie,
                          @mobiel, @verificatieLopend, @verificatieDateTime,
                          @geblokkeerd, 0, @verwijderd)";
            SqlParameter[] parameters = new SqlParameter[] {
                new SqlParameter("gebruikersnaam", _gebruikersnaam),
                new SqlParameter("profielschets", _profielschets),
                new SqlParameter("email", _emailadres),
                new SqlParameter("avatarLocatie", _avatarLocatie),
                new SqlParameter("mobiel", _mobiel),
                new SqlParameter("verificatieLopend", !_isGeverifieerd),
                new SqlParameter("verificatieDateTime", _verificatieTot),
                new SqlParameter("geblokkeerd", _isGeblokkeerd),
                new SqlParameter("verwijderd", _isVerwijderd),
            };

            // Query uitvoeren, en auto-increment-value opslaan in _id
            _id = Provider.Instance.ExecuteInsert(query, parameters);
        }

        public void Wijzig(string profielschets, string emailadres, string mobiel, string avatarLocatie)
        {
            // Waardes opslaan in opbject
            _profielschets = profielschets;
            if (emailadres != _emailadres)
            {
                _teVerifierenEmail = emailadres;
                _verificatieTot = DateTime.Now.AddMinutes(Properties.Settings.Default.VerificationTimeoutMinutes);
            }
            _mobiel = mobiel;
            _avatarLocatie = avatarLocatie;

            // UPDATE-query bouwen
            string query = "UPDATE [profile] SET [profile] = @profielschets, [verificationemail] = @email, [verificationpendingdatetime] = @verificatieTot, [mobilenumber] = @mobiel, [avatarlocation] = @avatarLocatie WHERE [profileid] = @id";

            // Paramaters opbouwen (verificationemail en verificationpendingdatetime zijn wat ingewikkelder,
            // omdat deze NULL kunnen zijn)
            SqlParameter sqlParamEmail = new SqlParameter("email", System.Data.SqlDbType.Text) { IsNullable = true };
            SqlParameter sqlParamVerificatieTot = new SqlParameter("verificatieTot", System.Data.SqlDbType.DateTime) { IsNullable = true };
            if (emailadres == _emailadres)
            {
                sqlParamEmail.Value = DBNull.Value;
                sqlParamVerificatieTot.Value = DBNull.Value;
            }
            else
            {
                sqlParamEmail.Value = _teVerifierenEmail;
                sqlParamVerificatieTot.Value = _verificatieTot;
            }
            SqlParameter[] parameters = new SqlParameter[] {
                new SqlParameter("id", _id),
                new SqlParameter("profielschets", profielschets),
                sqlParamEmail,
                sqlParamVerificatieTot,
                new SqlParameter("avatarLocatie", avatarLocatie),
                new SqlParameter("mobiel", mobiel),
            };

            // Query uitvoeren
            Provider.Instance.Execute(query, parameters);
        }

        /// <summary>
        /// Geeft alle berichten die deze persoon heeft geplaatst
        /// </summary>
        public List<Bericht> Berichten
        {
            get
            {
                // Berichten is alles zonder parent-message-id
                return BerichtenReacties("[parentmessid] IS NULL");
            }
        }

        /// <summary>
        /// Geeft alle reacties die deze persoon heeft geplaatst
        /// </summary>
        public List<Bericht> Reacties
        {
            get
            {
                // Reacties is alles met parent-message-id
                return BerichtenReacties("[parentmessid] IS NOT NULL");
            }
        }

        /// <summary>
        /// Geeft alle messages van deze gebruiker die voldoen aan condition
        /// </summary>
        /// <param name="condition"></param>
        /// <returns></returns>
        private List<Bericht> BerichtenReacties(string condition)
        {
            // Query opbouwen
            string query = "SELECT " + Bericht.Select + " FROM [message] WHERE [sender] = @sender AND [removed] = 0 AND " + condition;
            SqlParameter[] parameters = new SqlParameter[] 
            {
                new SqlParameter("sender", _id)
            };

            // Query uitvoeren en ophalen reader
            SqlDataReader reader = Provider.Instance.ExecuteReader(query, parameters);

            // Reader uitlezen en in list stoppen
            List<Bericht> result = new List<Bericht>();
            while (reader.Read())
            {
                result.Add(Bericht.FromReader(reader));
            }
            reader.Close();

            return result;
        }

        /// <summary>
        /// Maakt een nieuw bericht aan voor deze gebruiker
        /// </summary>
        /// <param name="tekst"></param>
        /// <returns></returns>
        public Bericht NieuwBericht(string tekst)
        {
            return new Bericht(this, tekst);
        }

        /// <summary>
        /// Controleerd of deze gebruiker is geblokkeerd door de meegegeven gebruiker
        /// </summary>
        /// <param name="geblokkeerdDoor">De mogelijk blokkerende gebruiker</param>
        /// <returns></returns>
        public bool IsGeblokkeerdDoor(Profiel geblokkeerdDoor)
        {
            // Query opbouwen
            string query = "SELECT [followid] FROM [follower] WHERE [subject] = @subject AND [follower] = @follower AND [blocked] = 1";
            SqlParameter[] parameters = new SqlParameter[]
            {
                // De gevolgde (subject) blokkeert ...
                new SqlParameter("subject", geblokkeerdDoor.Id),
                // de follower
                new SqlParameter("follower", this.Id),
            };

            SqlDataReader reader = Provider.Instance.ExecuteReader(query, parameters);

            // Als er een rij is, dan is die geblokkeerd, anders niet
            bool result = false;
            if (reader.Read())
            {
                result = true;
            }

            // Reader vrijgeven en retourneren
            reader.Close();
            return result;
        }

        /// <summary>
        /// Blokkeer de opgegeven gebruiker, zodat deze geen reacties meer
        /// mag plaatsen
        /// </summary>
        /// <param name="gebruiker"></param>
        public void BlokkeerGebruiker(Profiel gebruiker)
        {
            // Als die al geblokkeerd is, niet opnieuw doen
            if (gebruiker.IsGeblokkeerdDoor(this))
            {
                return;
            }

            // Query aanmaken
            string query =
                @"INSERT INTO [follower] ([subject], [follower], [blocked], [requestpending]) 
                    VALUES ( @subject, @follower, 1, 0)";
            SqlParameter[] parameters = new SqlParameter[]
            {
                // De gevolgde (subject) blokkeert ...
                new SqlParameter("subject", this.Id),
                // de follower
                new SqlParameter("follower", gebruiker.Id),
            };

            // En query executen
            Provider.Instance.Execute(query, parameters);
        }

        /// <summary>
        /// Deblokkeerd de opgegeven gebruiker zodat deze weer berichten kan plaatsen
        /// </summary>
        /// <param name="gebruiker"></param>
        public void DeblokkeerGebruiker(Profiel gebruiker)
        {
            // Query aanmaken
            string query = "DELETE FROM [follower] WHERE [subject] = @subject AND [follower] = @follower AND [blocked] = 1";
            SqlParameter[] parameters = new SqlParameter[]
            {
                // De gevolgde (subject) blokkeert ...
                new SqlParameter("subject", this.Id),
                // de follower
                new SqlParameter("follower", gebruiker.Id),
            };

            // En query executen
            Provider.Instance.Execute(query, parameters);
        }

        /// <summary>
        /// Geeft een lijst van alle gebruikers die geblokkeerd zijn
        /// door deze gebruiker
        /// </summary>
        public List<Profiel> GeblokkeerdeGebruikers
        {
            get
            {
                // Query aanmaken (de gevolgde [subject] blokkeert de follower)
                string query = "SELECT " + Select + " FROM [profile] INNER JOIN [follower] ON [profile].[profileid] = [follower].[follower] AND [follower].[blocked] = 1 AND [follower].[subject] = @subject";
                SqlParameter[] parameters = new SqlParameter[]
                {
                    new SqlParameter("subject", Id),
                };

                // Query uitvoeren en rijen in lijst zetten
                List<Profiel> result = new List<Profiel>();
                SqlDataReader reader = Provider.Instance.ExecuteReader(query, parameters);
                while (reader.Read())
                {
                    result.Add(FromReader(reader));
                }

                // Reader vrijgeven en returnen
                reader.Close();
                return result;
            }
        }

        public List<Profiel> Reageerders
        {
            get
            {
                List<Profiel> result = new List<Profiel>();

                // Query voor het ophalen van alle directe reacties:
                // SELECT .. FROM [message] AS [level0] 
                // INNER JOIN [message] AS [level1]
                // ON [level1].[parentmessid] = [level0].[messid] AND [level1].[removed] = 0
                // WHERE [level0].[sender] = @me AND [level0].[removed] = 0 AND [level0].[parentmessid] IS NULL

                // Reacties op reacties wordt dan
                // ... 
                // INNER JOIN [message] AS [level2]
                // ON [level2].[parentmessid] = [level1].[messid] AND [level2].[removed] = 0
                // ...

                // En dan telkens een stap dieper gaan totdat er geen resultaten meer
                // worden teruggegeven

                int level = 0;
                bool heeftResultaten = true;
                while (heeftResultaten)
                {
                    level++;
                    heeftResultaten = false;

                    // Query aanmaken
                    string query = "SELECT DISTINCT " + Select + "FROM [message] AS [level0]";
                    for (int i = 1; i <= level; i++)
                    {
                        query += " INNER JOIN [message] AS [level" + i + "] ON [level" + i + "].[parentmessid] = [level" + (i - 1) + "].[messid] AND [level" + i + "].[removed] = 0";
                    }
                    query += " INNER JOIN [profile] ON [profile].[profileid] = [level" + level + "].[sender]";
                    query += " WHERE [level0].[sender] = @me AND [level0].[removed] = 0 AND [level0].[parentmessid] IS NULL";
                    // Paramaters
                    SqlParameter[] parameters = new SqlParameter[] 
                    {
                        new SqlParameter("me", Id)
                    };

                    // Query uitvoeren
                    SqlDataReader reader = Provider.Instance.ExecuteReader(query, parameters);
                    while(reader.Read())
                    {
                        heeftResultaten = true;
                        // Toevoegen aan resultaat wanneer deze er nog niet in zit
                        int profileId = reader.GetInt32(0);
                        if (!result.Exists(profiel => profiel.Id == profileId))
                        {
                            result.Add(FromReader(reader));
                        }
                    }
                    reader.Close();
                }

                return result;
            }
        }

        public void Blokkeer()
        {
            // Als is al geblokkeerd, datum sinds niet overschrijven
            if (_isGeblokkeerd)
            {
                return;
            }

            // Object bijwerken
            _isGeblokkeerd = true;
            _geblokkeerdSinds = DateTime.Now;

            // Query opbouwen en uitvoeren
            string query = "UPDATE [profile] SET [tempblocked] = 1, [tempblockeddatetime] = @sinds WHERE [profileid] = @id";
            SqlParameter[] paramaters = new SqlParameter[]
            {
                new SqlParameter("sinds", _geblokkeerdSinds),
                new SqlParameter("id", _id)
            };
            Provider.Instance.Execute(query, paramaters);
        }

        public void Deblokkeer()
        {
            // Object bijwerken
            _isGeblokkeerd = false;

            // Query opbouwen en uitvoeren
            string query = "UPDATE [profile] SET [tempblocked] = 0 WHERE [profileid] = @id";
            SqlParameter[] paramaters = new SqlParameter[]
            {
                new SqlParameter("id", _id)
            };
            Provider.Instance.Execute(query, paramaters);
        }

        public void Verwijder()
        {
            // Als is al verwijderd, datum sinds niet overschrijven
            if (_isVerwijderd)
            {
                return;
            }

            // Object bijwerken
            _isVerwijderd = true;
            _verwijderdSinds = DateTime.Now;

            // Query opbouwen en uitvoeren
            string query = "UPDATE [profile] SET [removed] = 1, [removeddatetime] = @sinds WHERE [profileid] = @id";
            SqlParameter[] paramaters = new SqlParameter[]
            {
                new SqlParameter("sinds", _verwijderdSinds),
                new SqlParameter("id", _id)
            };
            Provider.Instance.Execute(query, paramaters);
        }

        /// <summary>
        /// Retourneert de verificatie-code die gebruikt moet worden.
        /// Geeft null indien er niets te verifieren valt.
        /// </summary>
        public string VerificatieCode
        {
            // Based on http://www.codeproject.com/Articles/463390/ -- 25-06-2013
            get
            {
                // Geheime salt
                string salt = "TE2cq5=o";
                string toEncrypt;
                if (!_isGeverifieerd)
                {
                    // Dit is de eerste verificatie (nieuw account)
                    toEncrypt = salt + _id.ToString();
                }
                else if (_teVerifierenEmail != null)
                {
                    // We moeten het e-mailadres verifieren, dus uniek
                    // voor deze verificatie zijn het e-mailadres en de Id
                    // van het profiel
                    toEncrypt = salt + _id.ToString() + _teVerifierenEmail;
                }
                else
                {
                    // Er valt niets te verifieren
                    return null;
                }

                byte[] toEncryptBytes = Encoding.ASCII.GetBytes(toEncrypt);
                byte[] hash = SHA1.Create().ComputeHash(toEncryptBytes);

                StringBuilder str = new StringBuilder();
                foreach(byte b in hash) 
                {
                    str.Append(b.ToString("x2"));
                }
                return str.ToString();
            }
        }

        /// <summary>
        /// Verifieert de lopende account-toevoeging/wijziging
        /// </summary>
        /// <param name="code"></param>
        /// <returns>True indien succesvol</returns>
        public bool Verifieer(string code)
        {
            if (code != VerificatieCode)
            {
                // Verkeerde code
                return false;
            }

            if (DateTime.Now.CompareTo(_verificatieTot) > 0)
            {
                // Verificatie verlopen
                return false;
            }

            if (!_isGeverifieerd)
            {
                // Nieuw account geverifieerd
                _isGeverifieerd = true;
                // Query en parameters
                string query = "UPDATE [profile] SET [verificationpending] = 0 WHERE [profileid] = @id";
                SqlParameter[] parameters = new SqlParameter[] 
                {
                    new SqlParameter("id", _id),
                };
                // Uitvoeren
                Provider.Instance.Execute(query, parameters);

                return true;
            }
            else
            {
                // Nieuw e-mailadres instellen
                _emailadres = _teVerifierenEmail;
                // Query en parameters
                string query = "UPDATE [profile] SET [email] = @email, [verificationemail] = NULL WHERE [profileid] = @id";
                SqlParameter[] parameters = new SqlParameter[] 
                {
                    new SqlParameter("email", _emailadres),
                    new SqlParameter("id", _id),
                };
                // Uitvoeren
                Provider.Instance.Execute(query, parameters);

                return true;
            }
        }


    }
}
